using System;
using Microsoft.Xna.Framework;

namespace LevelEditor
{
    public class PickingManager
    {
        private static PickingManager instance;
        public static PickingManager Instance
        {
            get
            {
                if (instance == null)
                    instance = new PickingManager();

                return instance;
            }
        }

        public void RayIntersectsTriangle(ref Ray ray,
                                  ref Vector3 vertex1,
                                  ref Vector3 vertex2,
                                  ref Vector3 vertex3, out float? result)
        {
            // Compute vectors along two edges of the triangle.
            Vector3 edge1, edge2;

            Vector3.Subtract(ref vertex2, ref vertex1, out edge1);
            Vector3.Subtract(ref vertex3, ref vertex1, out edge2);

            // Compute the determinant.
            Vector3 directionCrossEdge2;
            Vector3.Cross(ref ray.Direction, ref edge2, out directionCrossEdge2);

            float determinant;
            Vector3.Dot(ref edge1, ref directionCrossEdge2, out determinant);

            // If the ray is parallel to the triangle plane, there is no collision.
            if (determinant > -float.Epsilon && determinant < float.Epsilon)
            {
                result = null;
                return;
            }

            float inverseDeterminant = 1.0f / determinant;

            // Calculate the U parameter of the intersection point.
            Vector3 distanceVector;
            Vector3.Subtract(ref ray.Position, ref vertex1, out distanceVector);

            float triangleU;
            Vector3.Dot(ref distanceVector, ref directionCrossEdge2, out triangleU);
            triangleU *= inverseDeterminant;

            // Make sure it is inside the triangle.
            if (triangleU < 0 || triangleU > 1)
            {
                result = null;
                return;
            }

            // Calculate the V parameter of the intersection point.
            Vector3 distanceCrossEdge1;
            Vector3.Cross(ref distanceVector, ref edge1, out distanceCrossEdge1);

            float triangleV;
            Vector3.Dot(ref ray.Direction, ref distanceCrossEdge1, out triangleV);
            triangleV *= inverseDeterminant;

            // Make sure it is inside the triangle.
            if (triangleV < 0 || triangleU + triangleV > 1)
            {
                result = null;
                return;
            }

            // Compute the distance along the ray to the triangle.
            float rayDistance;
            Vector3.Dot(ref edge2, ref distanceCrossEdge1, out rayDistance);
            rayDistance *= inverseDeterminant;

            // Is the triangle behind the ray origin?
            if (rayDistance < 0)
            {
                result = null;
                return;
            }

            result = rayDistance;
        }

        public Vector3? ComputePicking(Terrain terrain, Vector2 mouseCoords)
        {
            Ray cursorRay = Camera.instance.ComputeRay(MainMenu.instance.viewWindow1.viewport, mouseCoords.X, mouseCoords.Y);
            Plane p = new Plane(Vector3.Up, 0.0f);
            float? intersect = cursorRay.Intersects(p);
            if (intersect == null)
                return null;
            Vector3 intersectPoint = (Vector3)(cursorRay.Position + cursorRay.Direction * intersect);

            Vector3 normal = Vector3.Up;
            Vector3 tangent = Vector3.Right;
            Vector3 bitangent = Vector3.Forward;

            Matrix tbn = new Matrix();
            tbn.M11 = tangent.X;
            tbn.M12 = tangent.Y;
            tbn.M13 = tangent.Z;

            tbn.M21 = bitangent.X;
            tbn.M22 = bitangent.Y;
            tbn.M23 = bitangent.Z;

            tbn.M31 = normal.X;
            tbn.M32 = normal.Y;
            tbn.M33 = normal.Z;

            tbn.M44 = 1.0f;

            Vector3 tanSpace_pos = Vector3.Transform(cursorRay.Position, tbn);
            Vector3 tanSpace_dir = Vector3.TransformNormal(cursorRay.Direction, tbn);

            Vector2 pointAt = new Vector2(tanSpace_pos.X, -tanSpace_pos.Z);
            Vector2 endPoint = new Vector2(intersectPoint.X, -intersectPoint.Z);
            Vector2 step = new Vector2(tanSpace_dir.X, -tanSpace_dir.Z);
            float maxHeight = 0.0f;
            Vector2 choosePoint = Vector2.Zero;
            while (Vector2.Distance(pointAt, endPoint) > 2.0f)
            {
                float height = 0.0f;
                if (height > maxHeight)
                {
                    maxHeight = height;
                    choosePoint = pointAt;
                }

                pointAt += step;
            }

            return new Vector3(pointAt.X, maxHeight * terrain.HeightMap.heighest, -pointAt.Y);
        }
    }
}